"""
内容提取器
用于从HTML中提取主要内容和元数据
"""
import logging
import os
import threading
import re
import json
from typing import Dict, Any, Optional
from urllib.parse import urlparse, urljoin
from datetime import datetime
from bs4 import BeautifulSoup
import html2text

# 获取日志记录器
logger = logging.getLogger(__name__)

class ContentExtractor:
    """内容提取器，用于从HTML中提取主要内容和元数据"""
    
    def __init__(self, output_format: str = "markdown", extract_mode: str = "main"):
        """
        初始化内容提取器
        
        参数:
            output_format: 输出格式，支持"markdown"和"text"
            extract_mode: 提取模式，"main"表示提取主要内容区域，"body"表示提取整个body
        """
        self.process_id = os.getpid()
        self.thread_id = threading.get_ident()
        logger.info(f"[PID:{self.process_id}][TID:{self.thread_id}] 初始化ContentExtractor")
        
        self.output_format = output_format
        self.extract_mode = extract_mode
        self.h2t = html2text.HTML2Text()
        self.h2t.ignore_links = False
        self.h2t.ignore_images = False
        self.h2t.ignore_tables = False
        self.h2t.body_width = 0  # 不限制行宽
        
        # 详细配置信息使用DEBUG级别
        logger.debug(f"输出格式: {output_format}, 提取模式: {extract_mode}")
    
    def extract_content(self, html_content: str, url: str = "") -> Dict[str, Any]:
        """
        从HTML中提取内容和元数据
        
        参数:
            html_content: HTML内容
            url: 网页URL，用于元数据提取
            
        返回:
            包含提取内容和元数据的字典
        """
        # 流程性操作用INFO级别
        logger.info(f"开始从HTML提取内容")
        
        try:
            # 解析HTML
            soup = BeautifulSoup(html_content, 'html.parser')
            logger.debug(f"HTML解析完成，大小: {len(html_content)} 字节")
            
            # 提取元数据
            metadata = self._extract_metadata(soup, url)
            logger.debug(f"元数据提取完成: {json.dumps(metadata, ensure_ascii=False)[:100]}...")
            
            # 处理相对URL路径
            if url:
                self._convert_relative_urls(soup, url)
                logger.debug(f"已将相对URL路径转换为绝对路径")
            
            # 提取主要内容
            content_html = self._extract_main_content(soup)
            logger.debug(f"原始HTML内容大小: {len(content_html)} 字节")
            
            # 将HTML转换为指定格式
            if self.output_format == "markdown":
                content = self.h2t.handle(content_html)
                logger.debug(f"HTML已转换为Markdown格式")
            elif self.output_format == "html":
                # 保持HTML格式
                content = content_html
                logger.debug(f"保留原始HTML格式")
            else:
                # 文本格式，移除HTML标签
                content = BeautifulSoup(content_html, 'html.parser').get_text()
                logger.debug(f"HTML已转换为纯文本格式")
            
            # 返回结果
            result = {
                "url": url,
                "metadata": metadata,
                "content": content,
                # "raw_html": content_html
            }
            
            # 使用INFO级别记录关键提取结果信息
            logger.info(f"内容提取完成，提取内容大小: {len(content)} 字符")
            
            return result
            
        except Exception as e:
            # 错误信息使用ERROR级别
            logger.error(f"内容提取过程中发生错误: {str(e)}", exc_info=True)
            raise
    
    def _extract_metadata(self, soup: BeautifulSoup, url: str = "") -> Dict[str, Any]:
        """
        从HTML中提取元数据
        
        参数:
            soup: BeautifulSoup对象
            url: 网页URL
            
        返回:
            包含元数据的字典
        """
        # 详细操作使用DEBUG级别
        logger.debug(f"开始提取元数据")
        
        metadata = {
            "title": "",
            "description": "",
            "keywords": [],
            "author": "",
            "publish_date": "",
            "domain": self._extract_domain(url)
        }
        
        # 提取标题
        title_tag = soup.find('title')
        if title_tag and title_tag.string:
            metadata["title"] = title_tag.string.strip()
        
        # 从meta标签提取信息
        for meta in soup.find_all('meta'):
            name = meta.get('name', '').lower()
            property = meta.get('property', '').lower()
            content = meta.get('content', '')
            
            if not content:
                continue
                
            # 描述
            if name == 'description' or property == 'og:description':
                metadata["description"] = content.strip()
                
            # 关键词
            elif name == 'keywords':
                metadata["keywords"] = [k.strip() for k in content.split(',')]
                
            # 作者
            elif name == 'author' or property == 'article:author':
                metadata["author"] = content.strip()
                
            # 发布日期
            elif name == 'article:published_time' or property == 'article:published_time':
                metadata["publish_date"] = content.strip()
        
        # 如果没有找到作者，尝试在HTML中查找
        if not metadata["author"]:
            author_elements = soup.select('.author, .byline, [rel=author]')
            if author_elements:
                metadata["author"] = author_elements[0].get_text().strip()
        
        # 如果没有找到发布日期，尝试在HTML中查找
        if not metadata["publish_date"]:
            date_elements = soup.select('time, .date, .published, [itemprop=datePublished]')
            if date_elements:
                date_text = date_elements[0].get_text().strip()
                # 尝试解析日期
                try:
                    # 这里可以添加更复杂的日期解析逻辑
                    metadata["publish_date"] = date_text
                except:
                    logger.debug(f"无法解析日期字符串: {date_text}")
        
        logger.debug(f"元数据提取完成: {metadata}")
        return metadata
    
    def _extract_domain(self, url: str) -> str:
        """
        从URL中提取域名
        
        参数:
            url: 完整URL
            
        返回:
            域名字符串
        """
        if not url:
            return ""
            
        try:
            parsed_url = urlparse(url)
            domain = parsed_url.netloc
            return domain
        except:
            logger.warning(f"无法从URL提取域名: {url}")
            return ""
    
    def _extract_main_content(self, soup: BeautifulSoup) -> str:
        """
        从HTML中提取主要内容
        
        参数:
            soup: BeautifulSoup对象
            
        返回:
            提取的HTML内容
        """
        # 详细提取操作记录DEBUG级别日志
        logger.debug(f"开始提取主要内容，提取模式: {self.extract_mode}")
        
        if self.extract_mode == "body":
            # 提取整个body内容
            body = soup.find('body')
            if body:
                # 移除脚本、样式和导航等干扰内容
                for element in body.select('script, style, nav, footer, .footer, header, .header, .ad, .advertisement, #sidebar, .sidebar'):
                    element.decompose()
                
                # 清理百度和其他网站的无效链接
                self._clean_invalid_links(body)
                
                logger.debug(f"已提取body内容，已移除干扰元素和无效链接")
                return str(body)
            else:
                logger.warning(f"未找到body标签")
                return str(soup)
        else:
            # 使用启发式算法提取主要内容
            main_content = self._extract_main_content_heuristic(soup)
            
            if main_content:
                # 清理无效链接
                self._clean_invalid_links(main_content)
                logger.debug(f"使用启发式算法成功提取主要内容")
                return str(main_content)
            else:
                # 如果启发式算法失败，回退到body模式
                logger.warning(f"启发式提取失败，回退到body模式")
                body = soup.find('body')
                if body:
                    # 移除脚本、样式和导航等干扰内容
                    for element in body.select('script, style, nav, footer, .footer, header, .header, .ad, .advertisement, #sidebar, .sidebar'):
                        element.decompose()
                    # 清理无效链接
                    self._clean_invalid_links(body)
                    return str(body)
                else:
                    return str(soup)
    
    def _extract_main_content_heuristic(self, soup: BeautifulSoup) -> Optional[BeautifulSoup]:
        """
        使用启发式算法提取主要内容区域
        
        参数:
            soup: BeautifulSoup对象
            
        返回:
            主要内容区域的BeautifulSoup对象，如果无法确定则返回None
        """
        # 详细算法过程使用DEBUG级别
        logger.debug(f"使用启发式算法提取主要内容")
        
        # 按优先级尝试提取主要内容区域
        content_selectors = [
            'article', 'main', '.main', '#main', '.content', '#content', 
            '.post', '.article', '.post-content', '.article-content'
        ]
        
        for selector in content_selectors:
            elements = soup.select(selector)
            if elements:
                element = elements[0]  # 使用第一个匹配的元素
                logger.debug(f"找到主要内容区域: {selector}")
                return element
        
        # 如果上述方法都无法找到主要内容，尝试使用段落密度算法
        paragraphs = soup.find_all('p')
        if paragraphs:
            # 简单的启发式：找到连续段落最多的父节点
            parent_scores = {}
            
            for p in paragraphs:
                if not p.get_text().strip():
                    continue
                    
                parent = p.parent
                if parent not in parent_scores:
                    parent_scores[parent] = 0
                parent_scores[parent] += 1
            
            if parent_scores:
                # 找出段落最多的父节点
                main_parent = max(parent_scores.items(), key=lambda x: x[1])[0]
                logger.debug(f"使用段落密度算法找到主要内容区域，包含段落数: {parent_scores[main_parent]}")
                return main_parent
        
        logger.warning(f"启发式算法无法确定主要内容区域")
        return None
    
    def _clean_invalid_links(self, content: BeautifulSoup) -> None:
        """
        清理无效链接和样式元素，特别针对百度页面优化
        
        参数:
            content: 要处理的BeautifulSoup对象
        """
        # 确保导入Comment类型
        from bs4 import Comment
        
        # 移除纯样式的链接（百度页面常见问题）
        for link in content.find_all('a'):
            # 检查链接是否为纯样式或空链接
            href = link.get('href', '')
            link_text = link.get_text().strip()
            
            # 移除没有文本内容的链接
            if not link_text:
                logger.debug(f"移除空链接: {href}")
                link.extract()
                continue
            
            # 移除无效href的链接
            if not href or href == '#' or href.startswith('javascript:'):
                logger.debug(f"移除无效href链接: {href}")
                link.extract()
                continue
                
            # 移除百度特有的样式链接（通常href中包含特定模式）
            if 'cache/static/protocol' in href or '.css' in href or '.gif' in href or '.png' in href:
                logger.debug(f"移除百度样式链接: {href}")
                link.extract()
                continue
                
            # 检查链接内是否只有样式标签
            if not link_text and (link.find('style') or link.find('img')):
                logger.debug(f"移除纯样式链接")
                link.extract()
                continue
        
        # 移除所有样式相关标签和元素
        self._remove_style_elements(content)
        
        # 移除无内容的span和div元素
        for element in content.find_all(['span', 'div']):
            if not element.get_text().strip() and not element.find_all(['img', 'video']):
                element.extract()
                
        # 移除所有注释
        for comment in content.find_all(text=lambda text: isinstance(text, Comment)):
            comment.extract()
    
    def _remove_style_elements(self, content: BeautifulSoup) -> None:
        """
        移除所有样式相关元素，特别针对百度页面
        
        参数:
            content: 要处理的BeautifulSoup对象
        """
        # 检查是否是百度页面，采用特殊处理
        if self._is_baidu_page(content):
            logger.info("检测到百度页面，使用专门的百度页面样式清理")
            self._clean_baidu_page_styles(content)
        
        # 1. 移除所有style标签
        style_tags = content.find_all('style')
        for style_tag in style_tags:
            logger.debug(f"移除样式标签: {style_tag.get('data-for', '无data-for属性')}")
            style_tag.decompose()
        
        # 2. 使用正则表达式处理可能被错误解析的style标签
        html_str = str(content)
        # 处理所有形式的style标签，包括<style data-for="result">等特殊格式
        style_pattern = re.compile(r'<style[^>]*>.*?</style>', re.DOTALL)
        html_str = style_pattern.sub('', html_str)
        
        # 处理没有正确闭合的style标签
        open_style_pattern = re.compile(r'<style[^>]*>')
        html_str = open_style_pattern.sub('', html_str)
        
        # 替换处理后的HTML字符串
        # 注意：通常不建议这样替换整个内容，但对于处理特殊的style标签是必要的
        new_soup = BeautifulSoup(html_str, 'html.parser')
        
        # 如果处理后的内容有效，则替换原始内容
        if new_soup and new_soup.find():
            # 清空原内容
            for child in list(content.children):
                child.extract()
            
            # 添加新内容
            for child in list(new_soup.children):
                content.append(child)
        
        # 3. 移除所有noscript标签（百度页面中常包含样式）
        for noscript in content.find_all('noscript'):
            logger.debug(f"移除noscript标签")
            noscript.decompose()
        
        # 4. 移除所有script标签
        for script in content.find_all('script'):
            logger.debug(f"移除script标签")
            script.decompose()
            
        # 5. 移除所有包含style属性的元素中的style属性
        for element in content.find_all(lambda tag: tag.has_attr('style')):
            logger.debug(f"移除元素的style属性: {element.name}")
            del element['style']
            
        # 6. 移除所有class属性（百度页面中大量使用class定义样式）
        for element in content.find_all(lambda tag: tag.has_attr('class')):
            logger.debug(f"移除元素的class属性: {element.name}")
            del element['class']
            
        # 7. 移除特定的百度样式相关标签
        for tag_name in ['link', 'meta', 'base']:
            for element in content.find_all(tag_name):
                logger.debug(f"移除{tag_name}标签")
                element.decompose()
                
        # 8. 移除data-为前缀的所有属性（百度页面大量使用）
        for element in content.find_all():
            for attr in list(element.attrs.keys()):
                if attr.startswith('data-'):
                    logger.debug(f"移除data-属性: {attr}")
                    del element[attr]
                    
        # 9. 再次尝试寻找并移除遗漏的style标签（保险措施）
        for style_tag in content.find_all('style'):
            style_tag.decompose()
            
        # 10. 检查并处理内联CSS，移除含有"style"文本的特定元素
        inline_style_text = content.find_all(string=lambda text: isinstance(text, str) and ('style' in text.lower() and '{' in text))
        for text in inline_style_text:
            if re.search(r'style.*\{.*\}', text, re.IGNORECASE | re.DOTALL):
                logger.debug(f"移除内联CSS文本")
                text.replace_with('')
    
    def _is_baidu_page(self, content: BeautifulSoup) -> bool:
        """
        检测是否为百度页面
        
        参数:
            content: 要检查的BeautifulSoup对象
            
        返回:
            布尔值，表示是否是百度页面
        """
        # 特征1: 检查包含"baidu"的链接
        baidu_links = content.find_all('a', href=lambda href: href and 'baidu.com' in href)
        if baidu_links:
            return True
            
        # 特征2: 检查百度特有的meta标签
        baidu_meta = content.find('meta', attrs={'content': lambda content: content and 'Baidu' in content})
        if baidu_meta:
            return True
            
        # 特征3: 检查style data-for属性
        data_for_styles = content.find_all('style', attrs={'data-for': True})
        if data_for_styles:
            return True
            
        # 特征4: 检查百度特有的样式类
        classes_with_baidu = content.find_all(lambda tag: tag.has_attr('class') and any('baidu' in c.lower() for c in tag.get('class', [])))
        if classes_with_baidu:
            return True
            
        return False
        
    def _clean_baidu_page_styles(self, content: BeautifulSoup) -> None:
        """
        专门处理百度页面的样式标签，使用更激进的方法
        
        参数:
            content: 要处理的BeautifulSoup对象
        """
        # 1. 字符串替换方法 - 最彻底的方法
        html_str = str(content)
        
        # 替换所有style标签，包括百度特有的data-for格式
        patterns = [
            r'<style\s+data-for="result"[^>]*>.*?</style>',  # 特别处理百度的data-for="result"样式
            r'<style\s+data-for=[^>]*>.*?</style>',  # 带data-for属性的style标签
            r'<style[^>]*>.*?</style>',                # 普通style标签
            r'<style[^>]*>',                          # 未闭合的style开始标签
            r'</style>',                              # 单独的style结束标签
        ]
        
        for pattern in patterns:
            html_str = re.sub(pattern, '', html_str, flags=re.DOTALL | re.IGNORECASE)
        
        # 2. 删除特定的百度样式字符串
        baidu_style_patterns = [
            r'html\{[^}]*\}',
            r'html body\{[^}]*\}',
            r'body\{[^}]*\}'
        ]
        
        for pattern in baidu_style_patterns:
            html_str = re.sub(pattern, '', html_str, flags=re.DOTALL | re.IGNORECASE)
            
        # 3. 特别处理可能被错误解析的样式标签
        # 这种情况下标签可能被HTML解析器错误理解为文本内容
        text_style_pattern = r'&lt;style\s+data-for=[^&]*&gt;.*?&lt;/style&gt;'
        html_str = re.sub(text_style_pattern, '', html_str, flags=re.DOTALL | re.IGNORECASE)
        
        # 直接查找并替换文本中的style标签（不依赖正则）
        html_str = html_str.replace('<style data-for="result"', '')
        html_str = html_str.replace('<style data-for=', '')
        html_str = html_str.replace('data-for="result"', '')
            
        # 4. 尝试用新的解析结果替换原内容
        try:
            new_soup = BeautifulSoup(html_str, 'html.parser')
            
            # 清空原内容
            for child in list(content.children):
                child.extract()
            
            # 添加新内容
            for child in list(new_soup.children):
                content.append(child)
                
            # 最后一次检查，确保所有style标签都被移除
            self._final_style_cleanup(new_soup)
                
            logger.debug("成功替换百度页面内容，清理样式")
        except Exception as e:
            logger.error(f"替换百度页面内容失败: {str(e)}")
            # 失败后不进行替换，继续使用常规方法清理
    
    def _final_style_cleanup(self, soup: BeautifulSoup) -> None:
        """
        最终清理，检查所有文本节点中是否还有style标签
        
        参数:
            soup: 要处理的BeautifulSoup对象
        """
        # 查找所有文本节点
        for text in soup.find_all(text=True):
            if '<style' in text or 'data-for=' in text:
                # 如果文本节点中包含style标签，尝试清理
                cleaned_text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL)
                cleaned_text = re.sub(r'<style[^>]*>', '', cleaned_text)
                cleaned_text = re.sub(r'data-for="[^"]*"', '', cleaned_text)
                
                # 替换文本节点
                text.replace_with(cleaned_text)
    
    def _convert_relative_urls(self, content: BeautifulSoup, base_url: str) -> None:
        """
        将内容中的相对URL转换为绝对URL
        
        参数:
            content: 要处理的BeautifulSoup对象
            base_url: 基础URL，用于构建绝对URL
        """
        if not base_url:
            logger.warning("没有提供基础URL，无法转换相对URL")
            return
            
        logger.debug(f"开始将相对URL转换为绝对URL，基础URL: {base_url}")
        
        # 处理链接
        for link in content.find_all('a', href=True):
            href = link['href']
            # 只处理相对URL
            if href and not href.startswith(('http://', 'https://', 'mailto:', 'tel:', 'ftp://')):
                absolute_url = urljoin(base_url, href)
                logger.debug(f"将相对链接URL转换为绝对链接: {href} -> {absolute_url}")
                link['href'] = absolute_url
        
        # 处理图片
        for img in content.find_all('img', src=True):
            src = img['src']
            # 只处理相对URL
            if src and not src.startswith(('http://', 'https://', 'data:')):
                absolute_url = urljoin(base_url, src)
                logger.debug(f"将相对图片URL转换为绝对链接: {src} -> {absolute_url}")
                img['src'] = absolute_url
                
        # 处理视频和音频
        for media in content.find_all(['video', 'audio', 'source', 'iframe'], src=True):
            src = media['src']
            # 只处理相对URL
            if src and not src.startswith(('http://', 'https://', 'data:')):
                absolute_url = urljoin(base_url, src)
                logger.debug(f"将相对媒体URL转换为绝对链接: {src} -> {absolute_url}")
                media['src'] = absolute_url
                
        # 处理其他可能包含URL的标签属性
        url_attributes = ['poster', 'background', 'data', 'formaction']
        for attr in url_attributes:
            for element in content.find_all(attrs={attr: True}):
                url = element[attr]
                # 只处理相对URL
                if url and not url.startswith(('http://', 'https://', 'data:')):
                    absolute_url = urljoin(base_url, url)
                    logger.debug(f"将相对URL({attr})转换为绝对链接: {url} -> {absolute_url}")
                    element[attr] = absolute_url 